package com.morpheus.zookeeper.api.lock.multi;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;

import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.utils.CloseableUtils;
import org.apache.zookeeper.data.Stat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.morpheus.zookeeper.api.lock.MultiLock;
import com.morpheus.zookeeper.config.ZookeeperProperties;

public class MultiDistributedLock implements MultiLock {
	private static final Logger LOGGER = LoggerFactory.getLogger(MultiDistributedLock.class);
	public static final String MULTI_LOCK_PATH = "lock/multi/lock";

	private Map<String, InterProcessMutex> lockMap = new ConcurrentHashMap<String, InterProcessMutex>();
	private ZookeeperProperties zookeeperProperties = null;
	private CuratorFramework curatorFramework = null;

	public MultiDistributedLock(CuratorFramework curatorFramework, ZookeeperProperties zookeeperProperties) {
		this.curatorFramework = curatorFramework;
		this.zookeeperProperties = zookeeperProperties;
	}

	private String getLockPath(String lockName) {
		String rootPath = this.zookeeperProperties.getRootPath();
		String lockPath = String.format("%s/%s/%s", rootPath, MULTI_LOCK_PATH, lockName);
		return lockPath;
	}

	@Override
	public void lock(String lockName) {
		try {
			String lockPath = this.getLockPath(lockName);
			InterProcessMutex interProcessMutex = null;
			if (this.lockMap.containsKey(lockName)) {
				interProcessMutex = this.lockMap.get(lockName);
			} else {
				interProcessMutex = new InterProcessMutex(this.curatorFramework, lockPath);
				this.lockMap.put(lockName, interProcessMutex);
			}
			interProcessMutex.acquire();
		} catch (Throwable th) {
			LOGGER.error("MultiDistributedLock.lock(lockName) throwable", th);
		}
	}

	@Override
	public void lock(String lockName, long time, TimeUnit unit) throws InterruptedException {
		try {
			String lockPath = this.getLockPath(lockName);
			InterProcessMutex interProcessMutex = null;
			if (this.lockMap.containsKey(lockName)) {
				interProcessMutex = this.lockMap.get(lockName);
			} else {
				interProcessMutex = new InterProcessMutex(this.curatorFramework, lockPath);
				this.lockMap.put(lockName, interProcessMutex);
			}
			interProcessMutex.acquire(time, unit);
		} catch (Throwable th) {
			LOGGER.error("MultiDistributedLock.lock(lockName, time, unit) throwable", th);
		}
	}

	@Override
	public void lockInterruptibly(String lockName) throws InterruptedException {

	}

	@Override
	public Condition newCondition(String lockName) {

		return null;
	}

	@Override
	public boolean tryLock(String lockName) {
		try {
			String lockPath = this.getLockPath(lockName);
			InterProcessMutex interProcessMutex = null;
			if (this.lockMap.containsKey(lockName)) {
				interProcessMutex = this.lockMap.get(lockName);
			} else {
				interProcessMutex = new InterProcessMutex(this.curatorFramework, lockPath);
				this.lockMap.put(lockName, interProcessMutex);
			}
			if (!interProcessMutex.isAcquiredInThisProcess()) {
				interProcessMutex.acquire();
				return true;
			}
		} catch (Throwable th) {
			LOGGER.error("MultiDistributedLock.tryLock(lockName) throwable", th);
		}
		return false;
	}

	@Override
	public boolean tryLock(String lockName, long time, TimeUnit unit) throws InterruptedException {
		try {
			String lockPath = this.getLockPath(lockName);
			InterProcessMutex interProcessMutex = null;
			if (this.lockMap.containsKey(lockName)) {
				interProcessMutex = this.lockMap.get(lockName);
			} else {
				interProcessMutex = new InterProcessMutex(this.curatorFramework, lockPath);
				this.lockMap.put(lockName, interProcessMutex);
			}
			if (!interProcessMutex.isAcquiredInThisProcess()) {
				interProcessMutex.acquire(time, unit);
				return true;
			}
		} catch (Throwable th) {
			LOGGER.error("MultiDistributedLock.tryLock(lockName, time, unit) throwable", th);
		}
		return false;
	}

	@Override
	public synchronized void unlock(String lockName) {
		try {
			String lockPath = this.getLockPath(lockName);
			InterProcessMutex interProcessMutex = null;
			if (this.lockMap.containsKey(lockName)) {
				interProcessMutex = this.lockMap.get(lockName);
			} else {
				interProcessMutex = new InterProcessMutex(this.curatorFramework, lockPath);
			}
			if (interProcessMutex.isAcquiredInThisProcess()) {
				interProcessMutex.release();
				this.lockMap.remove(lockName);
			}
			if (StringUtils.isNotEmpty(lockName)) {
				Stat stat = this.curatorFramework.checkExists().forPath(lockPath);
				if (stat != null) {
					this.curatorFramework.delete().deletingChildrenIfNeeded().forPath(lockPath);
				}
			}
		} catch (Throwable th) {
			LOGGER.error("MultiDistributedLock.unlock() throwable", th);
			throw new IllegalStateException(th);
		} finally {
			this.destory(lockName);
		}
	}

	@Override
	public void destory(String lockName) {
		if (MapUtils.isEmpty(this.lockMap)) {
			CloseableUtils.closeQuietly(this.curatorFramework);
			this.curatorFramework = null;
		}
		LOGGER.warn("Multi lock is not empty, curatorFramework cann't be destoried");
	}
}
